#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright (c) 2025 粤港澳大湾区（广东）国创中心

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

File: PythonObjectTree.py
Desc: A model object tree view with side panel for the application.
Author: xumanjia
Date: 2025-08-01
"""
import sys
import os
from PyQt5.QtWidgets import QFrame, QWidget, QTreeView, QHBoxLayout, QVBoxLayout, QPushButton, QStackedWidget, QLabel, QSizePolicy,\
      QStyledItemDelegate, QApplication, QAbstractItemView, QStyle
from PyQt5.QtGui import QIcon, QMouseEvent, QColor, QPainterPath, QKeyEvent, QStandardItemModel, QStandardItem
from PyQt5.QtCore import Qt, pyqtSignal, QSize, QEvent, QModelIndex, QPoint, QAbstractItemModel, QVariant, QRect, QTimer, QObject,\
      QItemSelection, QItemSelectionModel
from dataclasses import dataclass, field
from typing import List, Any, Optional, Union
import functools
from enum import Enum

@dataclass
class PythonTreeModelData:
    text: str = ""
    objName: str = ""

class PythonTreeView(QTreeView):
    #sigTreeItemSelected = pyqtSignal(list)
    treeBecameEmpty = pyqtSignal()

    def __init__(self, parent = None):
        super().__init__(parent)
        self.m_isRubberBandSelecting = False # 框选
        self.m_isCtrlPressed = False # ctrl键多选
        self.m_isShiftPressed = False # shift键多选
        self.m_selectionStartPos = QPoint()
        self.m_rect = QRect()
        self.m_path = QPainterPath()
        self.m_selectionTimer = QTimer()

        # 创建模型并绑定到当前视图
        self._model = QStandardItemModel()
        self._model.setHorizontalHeaderLabels(["模型"])
        self.setModel(self._model)

        self.setTreeViewProperty()
        self.setModel(self._model)
        self.setupConnections()
        self.installEventFilter(self)
        self.setMinimumWidth(200)

    def mousePressEvent(self, event: QMouseEvent):
        self.m_isRubberBandSelecting = False
        point = event.pos()
        index = self.indexAt(point) # 获取点击位置的模型索引
        if not index.isValid() and self.selectionModel():
            self.treeBecameEmpty.emit()
            self.selectionModel().clearSelection()
            self.checkAndTriggerSelectionChange()
        elif event.button() == Qt.LeftButton:
            self.m_selectionStartPos = event.pos()
        # 调用父类实现保持默认行为
        super().mousePressEvent(event)
    
    def mouseMoveEvent(self, event: QMouseEvent):
        if not event.buttons and Qt.LeftButton:
            return super().mouseMoveEvent(event)
        if not self.m_isRubberBandSelecting and (event.pos()-self.m_selectionStartPos).manhattanLength()>=QApplication.startDragDistance():
            self.m_isRubberBandSelecting = True
        super().mouseMoveEvent(event)
    
    def mouseReleaseEvent(self, event: QMouseEvent):
        if event.button()==Qt.LeftButton:
            self.m_isRubberBandSelecting = False
            self.checkAndTriggerSelectionChange()
        super().mouseReleaseEvent(event)

    def eventFilter(self, obj, event: QEvent):
        if event.type() == QEvent.KeyPress:
            keyEvent = QKeyEvent(event)
            if keyEvent.key() == Qt.Key_Control:
                self.m_isCtrlPressed = True
                return False
            elif keyEvent.key() == Qt.Key_Shift:
                self.m_isShiftPressed = True
                return False
        elif event.type() == QEvent.KeyRelease:
            keyEvent = QKeyEvent(event)
            if keyEvent.key() == Qt.Key_Control:
                self.m_isCtrlPressed = False
                self.checkAndTriggerSelectionChange()
                return False
            elif keyEvent.key() == Qt.Key_Shift:
                self.m_isShiftPressed = False
                self.checkAndTriggerSelectionChange()
                return False
        elif event.type() == QEvent.MouseButtonPress:
            if event.button() == Qt.LeftButton and self.m_isCtrlPressed:
                return  True 
            return False 
        return super().eventFilter(obj, event)
    
    def setModel(self, model):
        """重写 setModel，监听模型变化"""
        old_model = self.model()
        if old_model:
            old_model.rowsRemoved.disconnect(self.checkIfEmpty)
            old_model.modelReset.disconnect(self.checkIfEmpty)
        
        super().setModel(model)
        
        if model:
            model.rowsRemoved.connect(self.checkIfEmpty)
            model.modelReset.connect(self.checkIfEmpty)
        
        self.checkIfEmpty()  # 初始检查

    def update(self, new_data):
        self._model.clear()
        if not new_data:
            return
        for item_data in new_data:
            tree_item = QStandardItem(item_data.text)
            tree_item.setData(item_data, Qt.UserRole)
            self._model.appendRow(tree_item)
        
    # def getSelectedItem(self):
    #     selectedIndexes = self.selectionModel().selectedRows()
    #     if not selectedIndexes:
    #         return
    #     self.sigTreeItemSelected.emit(selectedIndexes)

    def onSelectionChanged(self, selected, deselected):
        self.m_selectionTimer.start()

    def checkAndTriggerSelectionChange(self):
        if not self.m_isRubberBandSelecting and not self.m_isCtrlPressed and not self.m_isShiftPressed:
            self.m_selectionTimer.stop()
            # self.getSelectedItem()
        else:
            self.m_selectionTimer.start(0)

    def setupConnections(self):
        self.selectionModel().selectionChanged.connect(self.onSelectionChanged)
        self.m_selectionTimer = QTimer(self)
        self.m_selectionTimer.setSingleShot(True)
        self.m_selectionTimer.setInterval(0)

    def getTreeItem(self, index:QModelIndex):
        if not index.isValid():
            return None
        model = self.model()
        if not model:
            return None
        # 正确方式：通过 model 的 itemFromIndex 方法获取 QStandardItem
        item = model.itemFromIndex(index)
        return item

    def setTreeViewProperty(self):
        self.setHeaderHidden(True)
        self.setRootIsDecorated(False)
        self.setMouseTracking(True)
        self.setIconSize(QSize(18, 18))

        self.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setContextMenuPolicy(Qt.CustomContextMenu)

    def checkIfEmpty(self):
        """检查树是否为空，并触发信号"""
        model = self.model()

        if model is None or model.rowCount() == 0:
            self.treeBecameEmpty.emit()

class PythonTreeViewDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        super().__init__(parent)

    def paint(self, painter, option, index):
        super().paint(painter, option, index)
        if option.state & QStyle.State_Selected:
            current_dir = os.path.dirname(os.path.abspath(__file__))
            icon = QIcon(current_dir + "/../resource/Tree/selected.svg")
            if not icon.isNull():
                icon_size = 16
                margin = 4
                icon_rect = QRect(
                    option.rect.right() - icon_size - margin,
                    option.rect.top() + (option.rect.height() - icon_size) // 2,
                    icon_size,
                    icon_size
                )
                icon.paint(painter, icon_rect)

class PythonTreeSidePannel(QStackedWidget):
    def __init__(self, parent = None):
        super().__init__(parent)

    def showContentPage(self, index):
        if index<0 or index>=self.count():
            return
        self.setCurrentIndex(index)
        self.setStyleSheet(
            """
            /* 窗体背景 */
            QFrame { 
                background: rgb(255, 255, 255);
                border: none;
            }
            
            """
        )

class PythonTreeSideBar(QFrame):
    # 定义自定义信号
    buttonClicked = pyqtSignal(QPushButton)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.mainLayout = QVBoxLayout(self)
        self.mainLayout.setContentsMargins(0,0,0,0)
        self.mainLayout.setSpacing(0)
        self.buttons = [] # 存储按钮引用
        self.buttonContainer = QWidget()
        self.buttonLayout = QVBoxLayout(self.buttonContainer)  # 垂直排列按钮
        self.buttonLayout.setContentsMargins(0, 0, 0, 0)
        self.buttonLayout.setSpacing(0)
        self.mainLayout.addWidget(self.buttonContainer)
        self.mainLayout.addStretch()
        self.pannel = PythonTreeSidePannel()

        self.setStyleSheet(
            """
            /* 窗体背景 */
            QFrame { 
                background: rgb(255, 255, 255);
                border: none;
            }
            /* QPushButton 样式 */
            QPushButton {
                background: transparent;
                border: none;
            }
            /* 保持按下状态样式 */
            QPushButton:checked {
                background-color: rgb(197, 219, 251);
            }
            /* 悬停状态样式 */
            QPushButton:hover {
                background-color: rgb(197, 219, 251);
            }
            """
        )
        
    def getCurrentIndex(self):
        if self.pannel is None:
            return 0
        return self.pannel.currentIndex()

    def setCurrentIndex(self, index):
        if 0 <= index < self.pannel.count():
            self.pannel.showContentPage(index)
        for button in self.buttons:
            btnIndex = button.property("sidePanelIndex")
            button.setChecked(btnIndex == index)

    def getCurrentWidget(self):
        if self.pannel is None:
            return None
        return self.pannel.currentWidget()
    
    def getWidget(self, index):
        if self.pannel is None:
            return None
        if index<0 or index>=self.pannel.count():
            return None
        return self.pannel.widget(index)

    def uncheckOtherButtons(self, button):
        checked = False
        for btn in self.buttons:
            if btn != button and btn.isChecked():
                btn.setChecked(False)
                checked = True
        return checked

    def setSidePannel(self, pannel):
        self.pannel = pannel

    def getButtonByObjName(self, objectName):
        return self.buttons.get(objectName, None)

    def addButtonDynamically(self, icon, buttonName, widget):
        button = QPushButton()
        button.setIcon(icon)
        button.setIconSize(QSize(24, 24))
        button.setObjectName(buttonName)
        button.setFixedSize(40, 40)
        button.setCheckable(True)
        button.clicked.connect(self.onButtonClicked)
        self.buttons.append(button)
        self.buttonLayout.addWidget(button)
        self.pannel.addWidget(widget)
        self.pannel.showContentPage(0)

    def onButtonClicked(self):
        button = self.sender() # 获取触发信号的按钮
        if button:
            self.buttonClicked.emit(button)

    def getButtonIndex(self, button):
        try:
            return self.buttons.index(button)
        except ValueError:
            return -1  # 如果按钮不在列表中，返回 -1
        
    def getButtonObjectName(self, button):
        if button and button in self.buttons:
            return button.objectName()
        return ""

class PythonObjectTreeTitleBar(QFrame):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.titleLabel = QLabel(self)
        self.titleLayout = QHBoxLayout(self)
        self.titleLayout.setContentsMargins(8, 0, 0, 0)
        self.titleLayout.addWidget(self.titleLabel)
        self.titleLayout.addStretch()
        self.setStyleSheet(
            """
            /* 窗体背景 */
            QFrame { 
                background: qlineargradient(x1:0.5, y1:0, x2:0.5, y2:1, stop:0 rgb(240, 242, 245), stop:1 rgb(211, 214, 217));
                border: none;
                height: 30px;
            }
            /* QLabel 样式 */
            QLabel {
                background: transparent;
            }
            """
        )

    def setTitle(self, title):
        self.titleLabel.setText(title)

    def getTitleLabel(self):
        return self.titleLabel

class PythonObjectTree(QFrame):
    sigSidePannelVisible = pyqtSignal(bool)

    def __init__(self, parent = None):
        super().__init__(parent)
        self.setupUI()

    def setupUI(self):
        current_dir = os.path.dirname(os.path.abspath(__file__))
        self.objectTreeView = self.createPythonTreeView()
        self.treeSidePannel = PythonTreeSidePannel()
        self.treeSideBar = PythonTreeSideBar()
        self.sideLayout = QHBoxLayout(self)
        self.sideLayout.setContentsMargins(0, 0, 0, 0)
        self.sideLayout.setSpacing(2)
        self.sideLayout.addWidget(self.treeSideBar)
        self.sideLayout.addWidget(self.treeSidePannel)
        self.setLayout(self.sideLayout)
        self.treeSideBar.setContentsMargins(0, 0, 0, 0)
        self.treeSideBar.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
        self.treeSideBar.setSidePannel(pannel=self.treeSidePannel)
        self.mIcon = QIcon(current_dir + '/../resource/Tree/object.svg')
        self.treeSideBar.addButtonDynamically(icon=self.mIcon, buttonName="对象树", widget=self.objectTreeView)
        self.treeSideBar.buttons[0].setChecked(True)
        
        # 连接自定义信号到槽函数
        self.treeSideBar.buttonClicked.connect(lambda button: self.onSideButtonClicked(button))

    def SetModelData(self, vData):
        return self.objectTreeView.update(vData)

    def ShowOrHideTreeSideBar(self, bShow):
        if self.treeSideBar:
            if bShow:
                self.treeSideBar.show()
            else:
                self.treeSideBar.hide()

    def SetSideCurrentIndex(self, index):
        if self.treeSideBar:
            self.treeSideBar.setCurrentIndex(index)

    def createPythonTreeView(self):
        self.treeView = PythonTreeView()
        self.delegate = PythonTreeViewDelegate(self.treeView)
        self.treeView.setItemDelegate(self.delegate)

        return self.treeView
    
    def changeShowWidget(self, index):
        if self.treeSidePannel and self.treeSidePannel.isVisible():
            if index != self.treeSidePannel.currentIndex():
                self.treeSidePannel.setCurrentIndex(index)

    def onSideButtonClicked(self, button:QPushButton):
        if not button:
            return
        checked = button.isChecked()

        if checked:
            if self.treeSidePannel:
                self.treeSidePannel.setVisible(True)
                self.sigSidePannelVisible.emit(True)
        else:
            if self.treeSidePannel:
                self.treeSidePannel.setVisible(False)
                self.sigSidePannelVisible.emit(False)
